#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# create a vxlan switch
# we create a bridge and then add the vxlan interface to it
#
# @param string _name name of the switch
#
switch::vxlan::init(){
    local _name="$1"
    local _id _vlan _if _maddr _addr _ifconf

    # see if the bridge already exists
    switch::standard::id "_id" "${_name}" && return 0

    # need a vlan id and interface
    config::core::get "_vlan" "vlan_${_name}"
    config::core::get "_if" "ports_${_name}"
    [ -z "${_vlan}" -o -z "${_if}" ] && return 1

    # get local address for this interface
    _local=$(ifconfig ${_if} | grep "inet " | cut -d" " -f 2)
    [ -z "${_local}" ] && return 1

    # come up with an ip address for multicast data
    switch::vxlan::__multicast "_maddr" "${_name}"

    # create the vxlan interface
    ifconfig "vxlan${_vlan}" create vxlanid "${_vlan}" vxlanlocal "${_local}" vxlangroup "${_maddr}" \
      vxlandev "${_if}" descr "vm-vxlan/${_switch}" group vm-vlan up >/dev/null 2>&1
    [ $? -eq 0 ] || return 1

    # get the length of the switch name
    # it's useful for other utilities to use switch name as interface name
    # as it's static. can't do that if it's > 12 chars
    _len=$(echo -n "${_name}" | wc -m)

    if [ ${_len} -le 12 ]; then
      _ifconf="name vm-${_name}"
    else
      _ifconf="descr vm/${_name}"
    fi

    # create a bridge for this switch
    _id=$(ifconfig bridge create ${_ifconf} group vm-switch up 2>/dev/null)
    [ $? -eq 0 ] || util::err "failed to create bridge interface for switch ${_name}"

    switch::set_viid "${_name}" "${_id}"

    # randomise mac if feature is available
    [ ${VERSION_BSD} -ge 1102000 ] && ifconfig "${_id}" link random

    # bridge vxlan to our guest switch
    # static route traffic for this multicast address via our specified interface
    ifconfig "${_id}" addm "vxlan${_vlan}"
    route add -net ${_maddr}/32 -iface ${_if} >/dev/null 2>&1

    # custom address for bridge?
    config::core::get "_addr" "addr_${_name}"
    [ -n "${_addr}" ] && ifconfig "${_id}" inet ${_addr}
}

# show the configuration details for a vxlan switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::vxlan::show(){
    local _name="$1"
    local _format="$2"
    local _id _vlan _port _addr _priv

    switch::standard::id "_id" "${_name}"
    config::core::get "_vlan" "vlan_${_name}"
    config::core::get "_port" "ports_${_name}"
    config::core::get "_addr" "addr_${_name}"
    config::core::get "_priv" "private_${_name}" "no"

    printf "${_format}" "${_name}" "vxlan" "${_id:--}" "${_addr:--}" "${_priv}" "n/a" "${_vlan}" "${_port}"
}

# create a vxlan switch
#
switch::vxlan::create(){

    # we must have an interface and vlan to use
    [ -z "${_if}" -o -z "${_vlan}" ] && util::err "vxlan switches must be created with an interface and vlan id specified"

    # store configuration
    config::core::set "switch_list" "${_switch}" "1"
    config::core::set "type_${_switch}" "vxlan"
    config::core::set "vlan_${_switch}" "${_vlan}"
    config::core::set "ports_${_switch}" "${_if}"

    [ -n "${_addr}" ] && config::core::set "addr_${_switch}" "${_addr}"
    [ -n "${_priv}" ] && config::core::set "private_${_switch}" "${_priv}"

    config::core::load
    switch::vxlan::init "${_switch}"
}

# destroy a vxlan switch
#
# @param string _switch the switch to remove
#
switch::vxlan::remove(){
    local _switch="$1"
    local _id _vlan _maddr
    
    # try to get guest bridge and vxlan id
    switch::standard::id "_id" "${_switch}"
    [ $? -eq 0 ] || return 1

    config::core::get "_vlan" "vlan_${_switch}"
    [ -z "${_vlan}" ] && return 1

    # get the multicast address we used for this switch
    # and try to remove any route we may have added
    switch::vxlan::__multicast "_maddr" "${_switch}"
    route del -net "${_maddr}/32" >/dev/null 2>&1

    # destroy the guest bridge
    ifconfig ${_id} destroy >/dev/null 2>&1
    [ $? -eq 0 ] || return 1

    # destroy the vxlan
    ifconfig "vxlan${_vlan}" destroy >/dev/null 2>&1
}

# add a new interface to this switch
# we only allow a single physical interface for
# vxlan switches. this must be set at creation time
# so this just reports an error
#
# @param string _switch name of the vxlan switch
# @param string _if the interface to add
#
switch::vxlan::add_member(){
    util::err "vxlan interface must be configured at creation time"
}

# remove an interface from a switch
# we don't support this here
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::vxlan::remove_member(){
    util::err "vxlan interface must be configured at creation time"
}

# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::vxlan::vlan(){
    util::err "vxlan id can only be set at creation time"
}

# get the multicast address for a vxlan switch
#
# @param string _var variable to put address into
# @param string _switch the switch name
#
switch::vxlan::__multicast(){
    local _var="$1"
    local _switch="$2"
    local _hash _l_addr _octet _pos

    # come up with an ip address for multicast data
    _hash=$(md5 -qs "${_switch}")
    _l_addr="239"

    for _pos in 1-2 3-4 5-6; do
        _octet=$(printf ".%d" "0x`echo "${_hash}"| cut -c ${_pos}`")
        _l_addr="${_l_addr}${_octet}"
    done

    setvar "${_var}" "${_l_addr}"
}
